home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 May: Tool Chest / Developer CD Series Tool Chest (Apple Computer)(May 1999).iso / Tool Chest / Games / Game Sample Code / ZAM 1.0a13 / GameSource / GameAEvents.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-09-16  |  13.8 KB  |  489 lines  |  [TEXT/KAHL]

  1. #include "GameAEvents.h"
  2. #include "ZAM.h"
  3. #include "WindowDispatch.h"
  4. #include "TankSprite.h"
  5. #include "MissileSprite.h"
  6. #include "CoreGlobals.h"
  7. #include "ZAMProtos.h"
  8.  
  9. #define rNotifySICN 128
  10. #define rWakeUpSND    128
  11. #define kDiamondMark 1
  12. #define rReqGameAlert 130
  13. #define AcceptBtnItem 1
  14.  
  15.  
  16.  
  17.  
  18. pascal OSErr AERequestGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
  19. pascal OSErr AEAcceptGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
  20. pascal OSErr AERefuseGame ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
  21. pascal OSErr AEAnswer (AppleEvent *theAE, AppleEvent *reply, long rfCon);
  22. pascal OSErr AEGoodbye (AppleEvent *theAE, AppleEvent *reply, long rfCon);
  23. pascal OSErr AEAckTime(AppleEvent *theAE, AppleEvent *reply, long rfCon);
  24. pascal OSErr AESynchTank ( AppleEvent *theAE, AppleEvent *reply, long rfCon);
  25.  
  26. Boolean    gByeNeeded;
  27. long    gLastSynchTime;
  28. long    gLocalTime;
  29. long    gLastReturnTime;
  30.  
  31.  
  32. void InstallCustomEvents(gamePtr game)
  33. /*
  34.     This installs the apple event handlers used by the game.
  35.     
  36. */
  37. {
  38.     OSErr            err = noErr;
  39.  
  40.     gByeNeeded = false;
  41.     gLastSynchTime = 0;
  42.     gLastReturnTime = 0;
  43.     gLocalTime = 0;
  44.  
  45.     err = AEInstallEventHandler (kCoreEventClass, kAEAnswer, AEAnswer, (long)game, false);
  46.     if(err) {
  47.         ErrMsgCode("\pCould not install AE handler.",err);
  48.         ExitToShell();
  49.     }
  50.  
  51.     err = AEInstallEventHandler (kZAMEventClass, kRequestGameID, AERequestGame,(long)game, false);
  52.     if(err) {
  53.         ErrMsgCode("\pCould not install AE handler.",err);
  54.         ExitToShell();
  55.     }
  56.  
  57.     err = AEInstallEventHandler (kZAMEventClass, kGoodByeID, AEGoodbye,(long)game, false);
  58.     if(err) {
  59.         ErrMsgCode("\pCould not install AE handler.",err);
  60.         ExitToShell();
  61.     }
  62.  
  63.     err = AEInstallEventHandler (kZAMEventClass, kTankSynchID, AESynchTank, (long)game, false);
  64.     if(err) {
  65.         ErrMsgCode("\pCould not install AE handler.",err);
  66.         ExitToShell();
  67.     }
  68. }
  69.  
  70.  
  71. void SendGoodBye(void)
  72. /*
  73.     When quit is selected it calls this function, which sends a message to the other player,
  74.     letting them know you are no longer around.
  75.     
  76.     It would be nice if the quit handler displayed even a simple dialog,
  77.     but this was a pain for my testing, so I made it just quit.  Of course a real
  78.     application would not behave this way.
  79. */
  80. {
  81.     AppleEvent        goodByeEvt;
  82.     AppleEvent        reply;
  83.     OSErr            err = noErr;
  84.     Boolean            disposeNeeded = false;
  85.  
  86. #ifdef NO_NET
  87.     return;
  88. #endif
  89.     
  90.     if(gByeNeeded) {        
  91.         err = AECreateAppleEvent(kZAMEventClass, kGoodByeID, &gGame->oppAddr,
  92.                      kAnyTransactionID, gGame->gameID, &goodByeEvt);
  93.         if(err != noErr) {
  94.             ErrMsgCode("\p Failure: SendGoodBye AECreateAppleEvent",err);
  95.         }
  96.             
  97.         if(err == noErr) {
  98.             disposeNeeded = true;
  99.             err = AESend(&goodByeEvt, &reply, kAENoReply + kAECanInteract,
  100.                      kAEHighPriority, 60 * 60, nil, nil);
  101.             if(err != noErr) {
  102.                 ErrMsgCode("\p SendGoodBye: AESend goodByeEvt",err);
  103.             }
  104.         }
  105.         
  106.         
  107.         if(disposeNeeded) {
  108.             AEDisposeDesc(&goodByeEvt);
  109.         }
  110.     }
  111. }
  112.  
  113. pascal OSErr AEGoodbye (AppleEvent *theAE, AppleEvent *reply, long rfCon)
  114. /*
  115.     This is the handler for when the remote mac has quit.
  116.     It does not display any warning dialog box, because as I mentioned before,
  117.     in a code-build-test cycle, it was too much to be navigating dialogs and stuff.
  118.     So, it just causes the program to quit without warning.
  119. */
  120. {
  121.     gDone = true;
  122.     gByeNeeded = false;
  123. }
  124.  
  125. pascal OSErr AEAnswer(AppleEvent *theAE, AppleEvent *reply, long rfCon)
  126. /*
  127.     This handler is used for the apple events that return a result asynchronously.
  128.     The type of reply is stored in the keyReturnIDAttr.  The only events that require
  129.     a reply are the request game event, and the synch event.  Another synch event will
  130.     not be sent until an ack is received.
  131. */
  132. {
  133.     long            actType,actSize;
  134.     short            retID;
  135.     OSErr            err= noErr;
  136.     
  137.     
  138.     err = AEGetAttributePtr(theAE, keyReturnIDAttr,  typeShortInteger, &actType,
  139.                             &retID, sizeof(short), &actSize);
  140.     if(err != noErr) {
  141.         ErrMsgCode("\pAEAnswer: AEGetAttr keyReturnIDAttr",err);
  142.     }
  143.  
  144.     if(err == noErr) {
  145.         switch(retID) {
  146.             case    kAcceptID:    err = AEAcceptGame(theAE, reply, rfCon);
  147.             break;
  148.             
  149.             case    kTimeID:    err = AEAckTime(theAE, reply, rfCon);
  150.             break;
  151.             
  152.             default:    ErrMsgCode("\pUnknown Return ID.",retID);
  153.             break;
  154.         }        
  155.     }
  156.     
  157.     return err;
  158. }
  159.  
  160.  
  161. pascal OSErr AEAckTime(AppleEvent *theAE, AppleEvent *reply, long rfCon)
  162. /*
  163.     just grab the time so the synch event will not flood the wire with
  164.     unhandled events.  This is not a REAL appleevent handler, it just
  165.     looks like one, because it was easier that way.  This is actually called
  166.     by the return event handler, above.
  167. */
  168. {
  169.     OSErr        err = 0;
  170.     long        len;
  171.     DescType    actualType;
  172.     long        synchTime;
  173.  
  174.     err = AEGetParamPtr(theAE, keySynchTime, typeLongInteger, &actualType, 
  175.             &gLastReturnTime, sizeof(long), &len);
  176.             
  177.     if(err != noErr) {
  178.         ErrMsgCode("\p Failed: AEAckTime keySynchTime, typeLongInteger",err);
  179.     }
  180.  
  181.     return err;
  182. }
  183.  
  184. pascal OSErr AEAcceptGame(AppleEvent *theAE, AppleEvent *reply, long rfCon)
  185. /*
  186.     This event comes back when the other player accepts the game.  Any ZAM game will
  187.     accept if it is not already playing.  I used to have code to allow you to type in
  188.     a name, and request a game, and the other player would be able to decide if they
  189.     wanted to or not.  Again, this really hampered my code-compile-test cycle, so I stripped
  190.     or commented it out.  You could probably revive it if you wanted to.
  191.     
  192.     This is not a REAL appleevent handler, it just
  193.     looks like one, because it was easier that way.  This is actually called
  194.     by the return event handler, above.
  195. */
  196. {
  197.     OSErr            err = noErr;
  198.     long            actualType,longSize;
  199.     Boolean            acceptFlag;
  200.  
  201.     
  202.     if(gGame == nil) {
  203.         ErrMsg("\pReceived accept when never made window");
  204.         err = paramErr;
  205.     }
  206.         
  207.     if(gGame->gameState == kWaitingForAccept) {
  208.         err = AEGetParamPtr(theAE, keyAnswer, typeBoolean, &actualType, 
  209.                 &acceptFlag, sizeof(Boolean), &longSize);
  210.         if(err != noErr) {
  211.             ErrMsgCode("\p Failed: AEAcceptGame AEGetParamPtr typeBoolean",err);
  212.         }
  213.         
  214.         if(err == noErr)
  215.             if(acceptFlag) {
  216.                 gGame->gameState = kGameInProgress;
  217.                 /* Set up tank indexes here */
  218.                 gGame->localTankIndex = 0;
  219.                 gGame->remoteTankIndex = 1;
  220.                 PlaceTankSprites(gGame);
  221.                 gByeNeeded = true;
  222.             } else {
  223.                 /* display rejection notice */
  224.                 ParamText("\pThat person has refused your challenge. Try another.",nil,nil,nil);
  225.                 (void) Alert(131,nil);        // evil numbers must go
  226.                 gGame->gameState = kWaitingForRequest;
  227.             }
  228.     }
  229.         
  230.     return err;
  231. }
  232.  
  233.  
  234. pascal OSErr AERequestGame( AppleEvent *theAE, AppleEvent *reply, long rfCon)
  235. /*
  236.     This BIG ugly function handles requests for a game from another player
  237.     and starts things rolling.  It changes the game state flag, but
  238.     this flag is mostly ignored by the rest of the program.  At one time
  239.     I was using it, but I was not setting it properly all the time, and there
  240.     were more important bugs to fix so I just crippled it.
  241.     
  242.     
  243. */
  244. {
  245.     OSErr            err = noErr;
  246.     short            itemHit;
  247.     DescType        returnedType;
  248.     Size            length;
  249.     long            replyID;
  250.     Str255            nameStr;
  251.     AEAddressDesc    targetAddress;
  252.     AppleEvent        replyEvent;
  253.     Boolean            acceptFlag = true;
  254.     ulong            tranID;
  255.  
  256.     if(err == noErr) {
  257.         if(gGame->gameState != kWaitingForRequest) {
  258.             /* just automagically refuse the game cause we are busy */
  259.             acceptFlag = false;
  260.             err = AEPutParamPtr(reply, keyAnswer, typeBoolean, &acceptFlag, sizeof(Boolean));
  261.             if(err != noErr)  {
  262.                 ErrMsgCode("\p AERequestGame: AEPutParamPtr keyAnswer FALSE",err);
  263.             }
  264.         } else {
  265.         
  266.             /* get the address of the person requesting we play */
  267.             if(err == noErr) {
  268.                 err = AEGetAttributeDesc ( theAE, keyAddressAttr, typeWildCard, &targetAddress);
  269.                 if(err != noErr) {
  270.                     ErrMsgCode("\p AERequestGame: AEGetAttr keyAddressAttr",err);
  271.                 }
  272.             }
  273.             
  274.             if(err == noErr) {
  275.                 err = AEGetAttributePtr(theAE, keyTransactionIDAttr, typeLongInteger, &returnedType,
  276.                                     &tranID, sizeof(long), &length);
  277.                 if(err != noErr) {
  278.                     ErrMsgCode("\p AEGetAttr keyTransactionIDAttr",err);
  279.                 }            
  280.             }
  281.             
  282.             if(err == noErr) {
  283.                 /* ask user if they want to play */
  284.                 /* NOTE:  this is where I used to display the dialog asking */
  285.                 /* if a game was wanted or not */
  286.                 /* now it always accepts */
  287.                     acceptFlag = true;
  288.                     if(err == noErr) {
  289.                         gGame->oppAddr = targetAddress;
  290.                         gGame->gameID = tranID;
  291.                         gGame->localTankIndex = 1;
  292.                         gGame->remoteTankIndex = 0;
  293.                         gGame->gameState = kGameInProgress;
  294.                         PlaceTankSprites(gGame);
  295.                         gByeNeeded = true;
  296.                     }
  297.                     else if(err == paramErr) {
  298.                         acceptFlag = false;
  299.                         err = noErr;
  300.                     }
  301.     
  302.                 if(err == noErr) {
  303.                     err = AEPutParamPtr(reply, keyAnswer, typeBoolean, &acceptFlag, sizeof(Boolean));
  304.                     if(err != noErr)  {
  305.                         ErrMsgCode("\p AERequestGame: AEPutParamPtr keyAnswer",err);
  306.                     }                    
  307.                 }
  308.             }
  309.         }
  310.     }                    
  311.     return err;
  312. }
  313.  
  314.  
  315. pascal OSErr AESynchTank ( AppleEvent *theAE, AppleEvent *reply, long rfCon)
  316. /*
  317.     This is the network heartbeat of the game.  It recieves the state of the remote machine.
  318.     
  319.     It does some things with AppleEvents for speed
  320.     reasons that a normal application would not want to do.  I send large structures
  321.     and arrays, instead of building platform independant AEDescLists and that sort of thing.
  322.     I send the current state of the tank, and the current state of all the missiles (from
  323.     a subroutine call, see the code).
  324. */
  325. {
  326.  
  327.     OSErr        err = 0;
  328.     long        len;
  329.     DescType    actualType;
  330.     long        synchTime;
  331.     TankStatus    tStatus;
  332.  
  333.     /* first thing is to get the synch time to see if this one 
  334.         should be ignored.  The synch time is saved off each time
  335.         so the network is not flooded with synch events.
  336.         It is very easy to flood a network with these things.
  337.         Sometimes a mac may be busy and miss a few events, and then when it gets back
  338.         to it, it caused a hyper-spurt of animation while the mac caught up
  339.         and processed the apple events.  This was undesireable for my sample, so
  340.         I implemented this strategy of synching */
  341.          
  342.     if(err == noErr) {
  343.         /* get the synch time to see if we should ignore this or not*/
  344.         err = AEGetParamPtr(theAE, keySynchTime, typeLongInteger, &actualType, 
  345.                 &synchTime, sizeof(long), &len);
  346.         if(err != noErr) {
  347.             ErrMsgCode("\p Failed: AESynchTank keyTankPosition, typefixPt",err);
  348.         }
  349.     }
  350.     
  351.     if(err == noErr) {
  352.         /* send the synch time back */
  353.         err = AEPutParamPtr(reply, keySynchTime, typeLongInteger, 
  354.                             &synchTime, sizeof(long));
  355.         if(err != noErr) {
  356.             ErrMsgCode("\p Failure: AESynchTank AEPutParamPtr reply",err);
  357.         }
  358.     }
  359.     
  360.     if(err == noErr) {
  361.         if(gLastSynchTime > synchTime)
  362.             err = 1;
  363.         else
  364.             gLastSynchTime = synchTime;
  365.     }
  366.  
  367.     /*
  368.         this gets the status of the tank from the event */
  369.     if(err == noErr) {
  370.         err = AEGetParamPtr(theAE, keyTankStatus, typeTankStatus, &actualType, 
  371.                 &tStatus, sizeof(TankStatus), &len);
  372.         if(err != noErr) {
  373.             ErrMsgCode("\p Failed: AESynchTank keyTankStatus typeTankStatus",err);
  374.         }
  375.     }
  376.     
  377.     /* if we got the status of the tank ok, then we call the Synch. Tank function
  378.         (See TankSprites.c) to make sure the tank is in the right place doin the right
  379.         thing.
  380.         
  381.         After that, the remote missiles are updated by passing the whole applevent
  382.         to  MissileSprites.c via ProcessMissilePositions.
  383.     */
  384.     if(err == noErr) {
  385.         SynchronizeTank(gGame, &tStatus.position, tStatus.direction, tStatus.speed);
  386.         err = ProcessMissilePositions(theAE);
  387.     }
  388.  
  389.     /* eat the synch time errors */
  390.     if(err == 1) err = noErr;
  391.  
  392.     return err;
  393. }
  394.  
  395.  
  396.  
  397.  
  398. Boolean TankSynchTask(xthing *xtp, spritePtr spr)
  399. /*
  400.     This is a timed task managed by the xthing time manager.
  401.     It sends the state of the tank and the state of the missiles
  402.     to the remote mac.
  403.     
  404.     See the notes above about abusing and sending structures using AppleEvents.
  405.     
  406.     Good.  Now that you have read that, I'll elaborate more.
  407.     One of the key concepts with scriptable AppleEvent programs is to
  408.     use tagged data so that any application can determine the format of
  409.     parameters and send them along in a happy manner.  This may require many
  410.     calls to AEPutParamPtr and such, which may also cause the AppleEvent record
  411.     to grow multiple times, and can cause a slow down for a real time application
  412.     like this.  So, to minimize this, I kind go against the grain of AppleEvents and
  413.     just send a big block of data.  The good design thing for me to have done, which I
  414.     didn't, would have been to isolate the network code a little more, so that any layer could
  415.     be plugged in.
  416. */
  417. {
  418.     OSErr            err = noErr;
  419.     AppleEvent        tankSynchEvent;
  420.     AppleEvent        reply;
  421.     tankInfoRec        *tInfo;
  422.     Boolean            disposeNeeded = false;
  423.     long            tickTime;
  424.     TankStatus        tStatus;
  425.     
  426.     if(gLastReturnTime != gLocalTime)  return true;
  427.  
  428.     tInfo = (tankInfoRec*)spr->refCon;
  429.     
  430.     err = AECreateAppleEvent(kZAMEventClass, kTankSynchID, &gGame->oppAddr,
  431.                  kTimeID, gGame->gameID, &tankSynchEvent);
  432.     if(err != noErr) {
  433.         ErrMsgCode("\p Failure: FireNetworkMissile AECreateAppleEvent",err);
  434.     }
  435.     
  436.     
  437.     /*
  438.         This is where the time stamp is incremented, and placed into the appleevnet.
  439.         This is sent back in the reply event */
  440.         
  441.     if(err == noErr) {
  442.         disposeNeeded = true;
  443.         gLocalTime++;
  444.         err = AEPutParamPtr(&tankSynchEvent, keySynchTime, typeLongInteger, 
  445.                             &gLocalTime, sizeof(long));
  446.         if(err != noErr) {
  447.             ErrMsgCode("\p Failure: AEPutParamPtr",err);
  448.         }
  449.     }
  450.  
  451.     /* load up the tank info and stuff it into the event */
  452.     tStatus.position = spr->loc;
  453.     tStatus.direction = tInfo->dir;
  454.     tStatus.speed = tInfo->speed;
  455.     
  456.     if(err == noErr) {
  457.         err = AEPutParamPtr(&tankSynchEvent, keyTankStatus, typeTankStatus, 
  458.                             &tStatus, sizeof(TankStatus));
  459.         if(err != noErr) {
  460.             ErrMsgCode("\p Failure: AEPutParamPtr keyTankStatus, typeTankStatus",err);
  461.         }
  462.     }
  463.     
  464.     /* getting a little more structured here, I call out to add the missile information */
  465.     /* to the event.  CAn you tell that I did the tank stuff first, and missile stuff
  466.        later? */
  467.     if(err == noErr) {
  468.         err = AppendMissilePositions(&tankSynchEvent);
  469.         if(err != noErr) {
  470.             ErrMsgCode("\pError appending missile positions.",err);
  471.         }
  472.     }
  473.  
  474.     if(err == noErr) {
  475.         err = AESend(&tankSynchEvent, &reply, kAEQueueReply + kAECanInteract,
  476.                  kAEHighPriority, kNoTimeOut, nil, nil);
  477.         if(err != noErr) {
  478.             ErrMsgCode("\p Failure: AESend",err);
  479.         }
  480.     }
  481.  
  482.     if(disposeNeeded) {
  483.         AEDisposeDesc(&tankSynchEvent);
  484.     }
  485.  
  486.     return true;
  487. }
  488.  
  489.